package com.example.asm

import com.android.build.api.instrumentation.*
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import org.objectweb.asm.commons.AdviceAdapter
import org.objectweb.asm.commons.Method


abstract class ThreadCreationTransformer : AsmClassVisitorFactory<InstrumentationParameters.None> {

    override fun isInstrumentable(classData: ClassData): Boolean {
        return true
    }

    override fun createClassVisitor(
        classContext: ClassContext, nextClassVisitor: ClassVisitor
    ): ClassVisitor {
        return ThreadCreationClassVisitor(nextClassVisitor)
    }

    private class ThreadCreationClassVisitor(nextVisitor: ClassVisitor) :
        ClassVisitor(Opcodes.ASM9, nextVisitor) {
        override fun visitMethod(
            access: Int,
            name: String?,
            descriptor: String?,
            signature: String?,
            exceptions: Array<out String>?
        ): MethodVisitor {
            val mv = super.visitMethod(access, name, descriptor, signature, exceptions)
            return ThreadConstructorVisitor(api, mv, access, name, descriptor)
        }
    }

    private class ThreadConstructorVisitor(
        api: Int, mv: MethodVisitor, access: Int, name: String?, descriptor: String?
    ) : AdviceAdapter(api, mv, access, name, descriptor) {
        override fun visitMethodInsn(
            opcodeAndSource: Int,
            owner: String?,
            name: String?,
            descriptor: String?,
            isInterface: Boolean
        ) {
            println(
                "opcodeAndSource=$opcodeAndSource, " +
                        "owner=$owner, name=$name, " +
                        "descriptor=$descriptor, " +
                        "isInterface=$isInterface"
            )
            if ((owner == "java/lang/Thread" && name == "<init>")
                || (owner == "kotlin/concurrent/ThreadsKt") && name == "thread\$default"
            ) {
                invokeStatic(
                    Type.getType("Lcom/example/demo/StackRecorder;"), Method("recordStack", "()V")
                )
            }
            super.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface)
        }
    }
}